<template>
  <div class="xmind-map-container">
    <div id="zx-xmind-map" :class="{ hidden: xmindOutlineVisible }" :style="{ 'background-color': gloabConfig?.backgroundColor || backgroundColor }" @click="handleXmindMapClick" @dblclick="handleXmindMapDbclick"></div>
    <transition :name="editorTypeName !== 'text' ? 'editor-fade-in' : null">
      <Editor v-if="editorVisible" @mousedown.stop @set-editable-value="setEditableValue" @real-time-update-node="realTimeUpdateSubject" :value="['summary', 'relation'].includes(editorTypeName) ? editorNodeValue : currentNode.data[editorTypeName]" :editorTypeName="editorTypeName" :subjectEditorStyle="subjectEditorStyle" />
    </transition>

    <ContextMenu v-show="contextVisible" :node="currentNode" :copyNode="copyNodeInstance" :copyStyle="copyStyle" :relationStyle="relationStyle" :copyRelationStyle="copyRelationStyle" :summaryStyle="summaryStyle" :copySummaryStyle="copySummaryStyle" :batchNodes="selectNodes" :type="contextMenuType" :sourceEvent="sourceEvent" @mousedown.stop @context-click="contextMenuClick" />

    <BottomActionBar :historyStep="historyStep" :currentStep="currentStep" :batchNodes="selectNodes" :isRoot="!currentNode?.parent" :nodeId="currentNode?.data._id" @mousedown.stop @h-click="handlerActionClick" />

    <transition name="tiezhi-fade-in">
      <IconSelectModal v-if="tiezhiIconVisible" :lengendVisible="lengendVisible" @mousedown.stop @select-tiezhi-icon="insertMarkerIcon" @switch-lengend-show="handleSwitchLengendShow" />
    </transition>

    <transition name="tiezhi-fade-in">
      <UploadImage v-if="uploadImageVisible" @mousedown.stop @set-image="setNodeImage" @close="uploadImageVisible = false" />
    </transition>

    <transition name="tiezhi-fade-in">
      <Latex v-if="latexVisible" :currentNode="currentNode" @mousedown.stop @insert-latex="updateTopicLatex" />
    </transition>

    <transition name="tiezhi-fade-in">
      <TiptapEditor v-if="commentVisible" :currentNode="currentNode" @mousedown.stop @get-comment-html="updateTopicCommentHtml" />
    </transition>

    <ThumbXMindMap ref="thumbXminRef" v-if="mapThumbVisible" :thumbBorderColor="thumbBorderColor" :backgroundColor="gloabConfig?.backgroundColor || backgroundColor" :scaleNumber="scaleNumber / 100" :boundingClient="boundingClient" @move-map="moveXmindThumbMap" />

    <CommandBar v-model="mapThumbVisible" :mindId="mindBasicInfo?.id" :canvasList="canvasMapList" :sheetIdx="sheetIdx" :scaleNumber="scaleNumber" :isMindMode="!xmindOutlineVisible" @set-scale="setXMindMapScale" @new-map="handleDeleteNewXmindMap" @copy-map="handdleCopyMindMap" @change-map="changeMindMapShow" @change-mode="changeMindMapMode" @rename-success="handleMapRename" />

    <transition name="tiezhi-fade-in">
      <ImportFile v-if="importFileVisible" :canvasList="canvasMapList" @new-map="(xmindJson, importType) => pushReplaceMindMapJson(xmindJson, importType)" @close="importFileVisible = false" />
    </transition>
    <transition name="scale-fade-in">
      <XmindOutline ref="mindOutlineRef" v-if="xmindOutlineVisible" />
    </transition>
    <transition name="tiezhi-fade-in">
      <TopicPick :currentNode="currentNode" :sheetIdx="sheetIdx" v-if="topicPickVisible" @close="topicPickVisible = false" @anchor-node="handleAnchorTopic" @insert-topiclink="handleInsertTopiclink" />
    </transition>
    <transition name="tiezhi-fade-in">
      <ShortKeyDescription v-if="shortKeyDescVisible" />
    </transition>
    <WaterMark v-if="gloabConfig?.watermark?.show" :backgroundColor="gloabConfig?.backgroundColor || backgroundColor" :content="gloabConfig?.watermark?.text" />
  </div>

  <SidebarTriggerContainer :skeletonTheme="canvasMapList[sheetIdx]" :currentSubject="selectNodes[selectNodes.length - 1]" :gloabConfig="gloabConfig" :relationStyle="relationStyle" :summaryStyle="summaryStyle" :backgroundColor="gloabConfig?.backgroundColor || backgroundColor" @update-skeleton="updateXMindSkeleton" @update-theme="updateXMindTheme" />
  <MenuBar :canvasList="canvasMapList" :mindBasicInfo="mindBasicInfo" @new-map="handleDeleteNewXmindMap" @export-xmind="handleExportMindMap" @import-file="importFileVisible = true" @search-map="handleXmindMapClick(); topicPickVisible = true" @display-shortkey="shortKeyDescVisible = true" />
</template>

<script setup>
import { ref, onMounted, nextTick, onBeforeUnmount } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { select, selectAll } from 'd3-selection'
import { zoom as d3Zoom, zoomIdentity } from 'd3-zoom'
import { ElMessage as message } from 'element-plus'
import rough from 'roughjs'
import mitter from '@/mitt'
import { apiGetMindDetails } from '@/apis/mind'
import themes from './themes'
import skeleton from './skeleton'
import { initXmindMapJson, debounceUpdateFileds } from './config'
import { preventWindowDefault, openWindowDefault, shortcutKeydown } from './shortcut-key'
import { generateNodeLinks } from './utils/layout'
import { collideRect } from './utils/math'
import {
  debounce,
  insertXmindNode,
  batchInsertXmindNode,
  deleteXmindNode,
  batchDeleteXmindNode,
  batchReferenceStyle,
  moveXmindNode,
  copyOrCutXmindNode,
  expandAllNodes,
  toogleExpandXmindNode,
  recursiveTreeValue,
  bacthUpdateNodeCustomStyle,
  gloabUpdateNodeCustomStyle,
  batchRecursiveTreeValue,
  storageRootRelaTime,
  getTargetDataById,
  batchResetNodeStyle,
  getNodeCustomStyle,
  unFoldTargetNodePath,
  generateRandomId,
  setJsonUniqueId
} from './utils'
import {
  createCanvasContainer,
  renderNewNodes,
  renderUpdateNodes,
  renderDeleteNodes,
  nodeHighLight,
  renderXmindOtherElement,
  renderNewEdges,
  renderUpdateEdges,
  renderDeleteEdges,
  drawAllSelectDomain,
  updateRenderVirtualRelationPath,
  drawXmindAuxiliaryElement,
  updateRedrawNodeStyle,
  gloabupdateColorStyle,
  realTimeUpdateNode,
  removeMarkHighlight,
  hideElementControlNode,
  removeRelationNodeHighLight,
  imageControlPointMove,
  getTopicCenterPosition,
  hiddenNodeHoverBorderPath,
  exitDrawRelation,
  dragRelationControlMove,
  drawRelationControlEnd,
  insertTopicRelation,
  drawRelationPathEnd,
  deleteRelationPath,
  deleteIconMark,
  imageControlMoveEnd,
  deleteTopicImage,
  graphLengends,
  updateRelationStyle,
  updateRelationFullStyle
} from './graph'
import { nodeHandlerMouseEnter, nodeHandlerMouseLeave, nodeHandlerDragIng, nodeHandlerDragEnd } from './plugins/nodeDragControl'
import {
  summaryHandlerControllerDraw,
  summaryControllerMove,
  moveSummaryControlEnd,
  removeSummaryNodeHighLight,
  updateSummaryText,
  summaryNodeHandleClick,
  handleDeleteSummary,
  handleInsertSummary,
  summaryHandlerMouseLeave,
  updateSummaryStyle,
  updateSummaryFullStyle
} from './plugins/summaryControl'
import { deleteNodeOutBorder, handleInsertOutBorder, outBorderNodeHandleClick, removeSelectOutBorder, showOutBorderTextInput, updateOutBorderText } from './plugins/outBorderControl'
import { handleInsertTask, handleTaskCheckboxClick } from './plugins/taskControl'
import { exportMinMapImage, exportMindMapSvg, exportMindMapToXmind, exportMindMapJson, exportMinMapPDF } from './export'
import { downloadLocalFile } from './export/downloadLocalFile'
import { newMindMapJson, deleteMindMapJson, mapJsonFullUpdate, generateCoverImage } from './utils/api'
import Editor from './components/Editor.vue'
import CommandBar from './components/CommandBar.vue'
import ThumbXMindMap from './components/ThumbXMindMap.vue'
import UploadImage from './components/UploadImage.vue'
import ContextMenu from './components/ContextMenu.vue'
import TiptapEditor from './components/TiptapEditor.vue'
import BottomActionBar from './components/BottomActionBar.vue'
import IconSelectModal from './components/IconSelectModal.vue'
import SidebarTriggerContainer from './components/SidebarTriggerContainer.vue'
import MenuBar from './components/MenuBar.vue'
import ImportFile from './components/ImportFile.vue'
import XmindOutline from './components/XmindOutline.vue'
import TopicPick from './components/TopicPick.vue'
import Latex from './components/Latex.vue'
import ShortKeyDescription from './components/ShortKeyDescription.vue'
import WaterMark from './components/WaterMark.vue'

const [route, router] = [useRoute(), useRouter()]
const mindId = route.params.id
let sheets = []
let displaySheet = null
let root = null
let svg = null
let rc = null
let mindContainer = null
let canvasWidth = 0
let canvasHeight = 0
let domainStart = null
let skeletonTheme = null
let relationId = null
let relationSourceId = null
let isPastState = null

const mindBasicInfo = ref(null)
const canvasMapList = ref([])
const sheetIdx = ref(0)
const gloabConfig = ref(null)
const thumbXminRef = ref(null)
const mindOutlineRef = ref()
const contextMenuType = ref(null)
const copyNodeInstance = ref(null)
const copyStyle = ref(null)
const sourceEvent = ref(null)
const editorTypeName = ref('text')
const scaleNumber = ref(100)
const currentNode = ref(null)
const selectNodes = ref([])
const editorVisible = ref(false)
const contextVisible = ref(false)
const tiezhiIconVisible = ref(false)
const uploadImageVisible = ref(false)
const latexVisible = ref(false)
const importFileVisible = ref(false)
const xmindOutlineVisible = ref(false)
const topicPickVisible = ref(false)
const commentVisible = ref(false)
const shortKeyDescVisible = ref(false)
const mapThumbVisible = ref(false)
const historyStep = ref()
const currentStep = ref(0)
const boundingClient = ref(null)
const editorNodeValue = ref(null)
const subjectEditorStyle = ref(null)
const thumbBorderColor = ref(null)
const backgroundColor = ref(null)
const lengendVisible = ref(null)
const relationStyle = ref(null)
const copyRelationStyle = ref(null)
const summaryStyle = ref(null)
const copySummaryStyle = ref(null)

onMounted(async () => {
  preventWindowDefault()
  shortcutKeydown()
  xmindMapEventMitters()
  shortcutKeyMitters()
  await queryMindDetails()
  initXmindContainer('zx-xmind-map')
  window.addEventListener('resize', resizeXmindMap)
  window.onbeforeunload = function () {
    generateCoverImage(mindId)
  }
})

onBeforeUnmount(() => {
  generateCoverImage(mindId)
  openWindowDefault()
  window.removeEventListener('resize', resizeXmindMap)
  window.onbeforeunload = null
  mitter.all.clear()
})

/**
 * 导图详细数据查询
 */
async function queryMindDetails () {
  try {
    const data = await apiGetMindDetails(mindId)
    const { id, name, isCollected, mindJson } = data
    mindBasicInfo.value = { id, name, isCollected }
    sheets = mindJson
    displaySheet = sheets[0]
    root = displaySheet.rootTopic
    canvasMapList.value = sheets.map(o => ({ name: o.name, id: o.id, skeleton: o.skeleton, theme: o.theme }))
    gloabConfig.value = JSON.parse(JSON.stringify(displaySheet.gloabConfig))
    historyStep.value = [JSON.parse(JSON.stringify(root))]
    lengendVisible.value = displaySheet.lengend?.visibility
    window.localStorage.setItem('sheets', JSON.stringify(sheets))
  } catch (error) {
    router.push('/home')
  }
}

/**
 * 初始化画布渲染所有元素
 * @param { String } id 生成画布容器id
 */
function initXmindContainer (id) {
  const ele = document.getElementById(id)
  canvasWidth = ele.clientWidth
  canvasHeight = ele.clientHeight
  const zoom = xmindContainerZoom()
  svg = select(ele).append('svg').attr('id', 'zx-xmind-map-svg')
    .attr('width', canvasWidth).attr('height', canvasHeight)
    .attr('xmlns', 'http://www.w3.org/2000/svg')
    .attr('xmlns:xlink', 'http://www.w3.org/1999/xlink')
    .on('contextmenu', outterContextClick)
    .call(zoom).on('dblclick.zoom', null)
  select('.xmind-map-container').on('mousedown', mouseStartMoveOnScreen).on('mousemove', mouseMoveIngOnScreen).on('mouseup', mouseEndMoveOnScreen)
  mindContainer = svg.append('g').attr('class', 'map-outter-container')
  initRenderCanvas()
  setXMindMapCenter()
}

function initRenderCanvas () {
  getSkeletonTheme()
  rc = rough.svg(mindContainer)
  createCanvasContainer(svg, rc)
  drawXmindAuxiliaryElement(skeletonTheme)
  const { compact, aligning } = gloabConfig.value || {}
  const { nodes = [], edges = [], relationNodes = [] } = generateNodeLinks(displaySheet.rootTopic, skeletonTheme, compact, aligning)
  renderNewNodes(nodes, skeletonTheme)
  renderNewEdges(edges)
  renderXmindOtherElement(undefined, relationNodes, skeletonTheme)
  xmindOutlineVisible.value && mindOutlineRef.value.setData(displaySheet)
  graphLengends(displaySheet.lengend, root)
}

/**
 * 导图主题和结构数据获取
 */
function getSkeletonTheme () {
  const skeletonSplit = displaySheet.skeleton.split('-')
  const themeSplit = displaySheet.theme.split('-')
  const cskeleton = skeleton[skeletonSplit[0]][skeletonSplit[1]]
  skeletonTheme = cskeleton[skeletonSplit[1]]({
    ...themes[themeSplit[0]][themeSplit[1]],
    ...themes[themeSplit[0]][themeSplit[1]][cskeleton.themeStyle]
  })
  sessionStorage.setItem('isRough', skeletonTheme.nodeRoughOptions ? '1' : '0')
  backgroundColor.value = skeletonTheme.backgroundColor
  thumbBorderColor.value = skeletonTheme.thumbColor
}

/**
 * 新增、编辑、删除节点后整体画布更新
 * @param { Boolean } clearable 更新画布的时候是否需要清除当前选中的节点
 * @param { Boolean } appendHistory 更新画布的时候是否需要把当前数据存入历史数据中
 */
function updateXmindCanvas (clearable = true, appendHistory = true, summaryId) {
  getSkeletonTheme()
  const { compact, aligning } = gloabConfig.value
  const { nodes = [], edges = [], relationNodes = [] } = generateNodeLinks(displaySheet.rootTopic, skeletonTheme, compact, aligning)
  renderNewNodes(nodes, skeletonTheme)
  renderUpdateNodes(nodes, skeletonTheme)
  renderDeleteNodes(nodes)
  renderNewEdges(edges)
  renderUpdateEdges(edges)
  renderDeleteEdges(edges)
  renderXmindOtherElement(summaryId, relationNodes, skeletonTheme)
  graphLengends(displaySheet.lengend, root)
  storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
  if (clearable) {
    hiddenMapModals()
    removeNodeHighLight()
  } else {
    selectAll('.select-node').classed('select-node', false).select('.select-rect-border').remove()
    for (let i = 0; i < selectNodes.value.length; i++) {
      nodeHighLight(selectNodes.value[i].data._id, skeletonTheme)
    }
  }
  removeSelectOutBorder()
  hideElementControlNode()
  removeSummaryNodeHighLight()
  removeRelationNodeHighLight()
  contextVisible.value = false
  appendHistory && appendXmindHistory()
  hiddenNodeHoverBorderPath()
  thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
}

/**
 * 构造画布缩放可拖动zoom实例
 */
function xmindContainerZoom (duration = 0) {
  return d3Zoom().scaleExtent([0.1, 5])
    .filter(event => {
      return (!event.ctrlKey && event.buttons === 1) || (event.ctrlKey && event.buttons === 0) || (!event.ctrlKey && event.buttons === 0)
    })
    .wheelDelta(event => -event.deltaY * (event.deltaMode ? 120 : 1) / 500)
    .on('zoom', function (event) {
      const ctrlKey = event.sourceEvent?.ctrlKey
      const buttons = event.sourceEvent?.buttons
      const deltaY = event.sourceEvent?.deltaY
      const deltaMode = event.sourceEvent?.deltaMode
      if ((!ctrlKey && buttons === 1) || (ctrlKey && buttons === 0)) {
        const { x: px, y: py } = displaySheet.eventTransform
        const { x, y } = event.transform
        const [dx, dy] = [px - x, py - y]
        if (!ctrlKey && buttons === 1 && !select(this).classed('grabbing') && (dx !== 0 || dy !== 0)) {
          select(this).classed('grabbing', true)
        }
        mindContainer.attr('transform', () => {
          displaySheet.eventTransform = event.transform
          return displaySheet.eventTransform
        })
        scaleNumber.value = Math.round(event.transform.k * 100)
        commentVisible.value = false
      } else if (!ctrlKey && buttons === 0) {
        mindContainer.attr('transform', () => {
          const x = displaySheet.eventTransform.x
          const k = displaySheet.eventTransform.k
          const y = displaySheet.eventTransform.y - deltaY * (deltaMode ? 120 : 1)
          displaySheet.eventTransform = event.transform
          displaySheet.eventTransform.y = y
          displaySheet.eventTransform.k = k
          displaySheet.eventTransform.x = x
          return displaySheet.eventTransform
        })
        commentVisible.value = false
      } else if (!event.sourceEvent) {
        mindContainer.transition().duration(duration).attr('transform', () => {
          displaySheet.eventTransform = event.transform
          scaleNumber.value = Math.round(event.transform.k * 100)
          return displaySheet.eventTransform
        })
      }
      setTimeout(() => {
        const { top, right, bottom, left } = mindContainer.node().getBoundingClientRect()
        boundingClient.value = {
          top, right, bottom, left
        }
      }, duration)
    })
    .on('end', function () {
      select(this).classed('grabbing', false)
    })
}

/**
 * 定位到根节点
 */
function setXMindMapCenter (transform, duration) {
  const zoom = xmindContainerZoom(duration)
  nextTick(() => {
    if (!transform) {
      svg.call(zoom.transform, zoomIdentity.translate(0, 0).scale(1))
      const xmindRootNode = svg.select('.x-mind-root-theme')
      const { width: RW, height: RH } = xmindRootNode.node().getBoundingClientRect()
      const offsetX = ['org.xmind.ui.brace.right', 'org.xmind.ui.logic.right'].includes(skeletonTheme.structure) ? 200 : (canvasWidth - RW) / 2
      const offsetY = ['org.xmind.ui.org-chart.down', 'org.xmind.ui.tree.right', 'org.xmind.ui.timeline.vertical'].includes(skeletonTheme.structure) ? 150 : (canvasHeight - RH) / 2
      svg.call(zoom.transform, zoomIdentity.translate(offsetX, offsetY).scale(1))
    } else {
      const { x, y, k } = transform
      svg.call(zoom.transform, zoomIdentity.translate(x, y).scale(k))
    }
  })
}

function moveXmindThumbMap ({ x, y }) {
  const scalceRadio = 0.1 / (scaleNumber.value / 100)
  const zoom = xmindContainerZoom()
  svg
    .call(zoom.transform,
      zoomIdentity
        .translate(displaySheet.eventTransform.x - x / scalceRadio, displaySheet.eventTransform.y - y / scalceRadio)
        .scale(displaySheet.eventTransform.k))
}

function resizeXmindMap () {
  const container = select('#zx-xmind-map').node()
  canvasWidth = container.clientWidth
  canvasHeight = container.clientHeight
  svg.attr('width', canvasWidth).attr('height', canvasHeight)
}

/**
 * 新建/导入/删除画布
 * @param {*} idx
 * @param {*} deleteFlag 代表删除画布
 * @param {*} mindJson 如果存在则指定json导入
 */
async function handleDeleteNewXmindMap (idx, deleteFlag) {
  if (deleteFlag) {
    // 删除画布
    await deleteMindMapJson(mindBasicInfo.value.id, sheets[idx].id)
    sheets.splice(idx, 1)
    if (idx !== sheetIdx.value) {
      sheetIdx.value = idx < sheetIdx.value ? sheetIdx.value - 1 : sheetIdx.value
      canvasMapList.value = sheets.map(o => ({ name: o.name, id: o.id, skeleton: o.skeleton, theme: o.theme }))
    } else {
      sheetIdx.value = idx === 0 ? 0 : idx - 1
    }
  } else {
    // 新增画布
    if (canvasMapList.value.length === 5) return
    const mindJson = initXmindMapJson()
    await newMindMapJson(mindBasicInfo.value.id, mindJson)
    sheets.push(mindJson)
    sheetIdx.value = sheets.length - 1
    window.localStorage.setItem('sheets', JSON.stringify(sheets))
  }
  updateCurrentMapStatistic()
}

/**
 * 导图内画布复制
 * @param {*} mapId
 */
async function handdleCopyMindMap (mapId) {
  const mindJson = sheets.find(m => m.id === mapId)
  const copyMindJson = JSON.parse(JSON.stringify(mindJson))
  copyMindJson.name = `${copyMindJson.name}-副本`
  copyMindJson.id = generateRandomId()
  setJsonUniqueId(copyMindJson.rootTopic, true)
  pushReplaceMindMapJson(copyMindJson, 'push')
}

/**
 * 指定的导图画布JSON替换或者新建
 * @param {*} mindJson
 * @param {*} type
 */
async function pushReplaceMindMapJson (mindJson, type) {
  if (type === 'replace') {
    const mapId = sheets[sheetIdx.value].id
    mindJson.id = mapId
    await mapJsonFullUpdate(mindBasicInfo.value.id, mapId, mindJson)
    sheets[sheetIdx.value] = mindJson
  } else if (type === 'push') {
    await newMindMapJson(mindBasicInfo.value.id, mindJson)
    sheets.push(mindJson)
    sheetIdx.value = sheets.length - 1
  }
  window.localStorage.setItem('sheets', JSON.stringify(sheets))
  updateCurrentMapStatistic()
  hiddenMapModals()
}

/**
 * 画布切换
 * @param {*} idx
 */
function changeMindMapShow (idx) {
  sheetIdx.value = idx
  updateCurrentMapStatistic()
}

/**
 * 画布切换后本地静态数据更新
 */
function updateCurrentMapStatistic () {
  displaySheet = sheets[sheetIdx.value]
  gloabConfig.value = displaySheet.gloabConfig
  root = displaySheet.rootTopic
  lengendVisible.value = displaySheet.lengend?.visibility
  historyStep.value = [JSON.parse(JSON.stringify(root))]
  currentStep.value = 0
  svg.remove()
  initXmindContainer('zx-xmind-map')
  canvasMapList.value = sheets.map(o => ({ name: o.name, id: o.id, skeleton: o.skeleton, theme: o.theme }))
  thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
  storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
}

/**
 * 导图模式和大纲模式切换
 */
function changeMindMapMode () {
  if (!xmindOutlineVisible.value) {
    xmindOutlineVisible.value = true
    nextTick(() => {
      mindOutlineRef.value.setData(displaySheet)
    })
  } else {
    const xmindJson = mindOutlineRef.value.getNewMindJson()
    xmindOutlineVisible.value = false
    if (xmindJson) {
      pushReplaceMindMapJson(xmindJson, 'replace')
    }
  }
}

/**
 * 画布重命名成修改静态数据
 * @param {} detail
 */
function handleMapRename (detail) {
  const { mapId, name } = detail
  const idx = sheets.findIndex(m => m.id === mapId)
  sheets[idx].name = name
  canvasMapList.value[idx].name = name
}

function setXMindMapScale (k) {
  const zoom = xmindContainerZoom()
  svg
    .call(zoom.transform,
      zoomIdentity
        .translate(displaySheet.eventTransform.x, displaySheet.eventTransform.y)
        .scale(k))
}

function handleXmindMapDbclick (event) {
  if (sourceEvent.value?.x !== event.x || sourceEvent.value.y !== event.y) return
  sourceEvent.value = event
  setTimeout(() => {
    contextMenuClick('insert-independent')
  })
}

function updateSelectNodeHighLight (event) {
  const selections = select('.mind-map-nodebox').selectAll('.x-mind-nodetheme')
  removeNodeHighLight()
  selections.nodes().forEach(selection => {
    const collide = collideRect(
      { x: Math.min(domainStart.x, event.x), y: Math.min(domainStart.y, event.y), width: Math.abs(event.x - domainStart.x), height: Math.abs(event.y - domainStart.y) },
      selection.getBoundingClientRect()
    )
    if (collide) {
      selectNodes.value.push(select(selection).datum())
      const id = select(selection).datum().data._id
      nodeHighLight(id, skeletonTheme)
    }
  })
}

function nodeHandlerDblclick (event, _this) {
  event.stopPropagation()
  removeNodeHighLight()
  hideElementControlNode()
  currentNode.value = select(_this).datum()
  nodeHighLight(currentNode.value.data._id, skeletonTheme)
  selectNodes.value.push(currentNode.value)
  showNodeTextEditor(select(_this))
  select(_this).raise()
}

function showNodeTextEditor (selection) {
  editorTypeName.value = 'text'
  editorVisible.value = true
  const textNode = selection.select('.node-text-description').node()
  const computedStyleCss = window.getComputedStyle(textNode)
  const style = {
    fontFamily: computedStyleCss.getPropertyValue('font-family'),
    fontSize: computedStyleCss.getPropertyValue('font-size'),
    fontWeight: computedStyleCss.getPropertyValue('font-weight'),
    color: computedStyleCss.getPropertyValue('color'),
    lineBreak: computedStyleCss.getPropertyValue('line-break'),
    wordBreak: computedStyleCss.getPropertyValue('word-break'),
    writingMode: computedStyleCss.getPropertyValue('writing-mode')
  }
  const width = textNode.getBoundingClientRect().width
  const { top, left } = selection.select('.x-mind-node-text').node().getBoundingClientRect()
  subjectEditorStyle.value = {
    top,
    left,
    maxWidth: currentNode.value.data.customStyle?.maxWidth || 500,
    fontStyle: currentNode.value.data.customStyle?.fontStyle,
    id: currentNode.value.data._id,
    k: displaySheet.eventTransform?.k || 1,
    ...style
  }
  selection.select('.latex-span').style('margin-left', `${width / subjectEditorStyle.value.k}px`)
  textNode.style.display = 'none'
}

function nodeHandlerClick (event, _this) {
  event.stopPropagation()
  removeSummaryNodeHighLight()
  removeRelationNodeHighLight()
  removeSelectOutBorder()
  hideElementControlNode()
  if (!event.altKey) {
    removeNodeHighLight()
    currentNode.value = select(_this).datum()
    const id = currentNode.value.data._id
    nodeHighLight(id, skeletonTheme)
    selectNodes.value.push(currentNode.value)
  } else {
    const data = select(_this).datum()
    const id = data.data._id
    const hasSelect = select(`#${id}`).classed('select-node')
    if (hasSelect) {
      select(`#${id}`).classed('select-node', false).select('.select-rect-border').remove()
      const idx = selectNodes.value.findIndex(n => n.data._id === data.data._id)
      selectNodes.value.splice(idx, 1)
    } else {
      nodeHighLight(id, skeletonTheme)
      selectNodes.value.push(data)
    }
    if (selectNodes.value.length > 1) {
      currentNode.value = null
    } else {
      currentNode.value = selectNodes.value[0] || null
    }
  }
}

/**
 * 画布定位到指定的主题
 * @param {*} id
 * @param {*} mapId 画布id
 */
function handleAnchorTopic (id, mapId) {
  if (mapId && mapId !== displaySheet.id) {
    const idx = sheets.findIndex(sheet => sheet.id === mapId)
    if (idx === -1) {
      return message.error('画布不存在')
    }
    changeMindMapShow(idx)
  }
  const update = unFoldTargetNodePath(displaySheet.rootTopic, id)
  if (typeof update === 'string') return message.error('目标主题不存在')
  update && updateXmindCanvas(false)
  removeNodeHighLight()
  const { x, y } = getTopicCenterPosition(id)
  setXMindMapCenter({ x, y, k: 1 }, 300)
  nextTick(() => {
    batchSelectTargetNodes([id])
  })
}

/**
 * 插入画布主题锚点连接
 * @param {*} mapId 画布id
 * @param {*} topicId 主题id
 */
function handleInsertTopiclink (mapId, topicId) {
  recursiveTreeValue(root, currentNode.value?.data._id, 'topiclink', {
    mapId,
    topicId
  })
  updateXmindCanvas()
}

function outterContextClick (event) {
  if (event.target.nodeName === 'svg') {
    removeRelationNodeHighLight()
    contextMenuType.value = 'global'
  }
  sourceEvent.value = event
  contextVisible.value = true
}

/**
 * 输入文案实时更新主题位置和大小信息
 * @param {*} computedStyle
 */
function realTimeUpdateSubject (computedStyle) {
  realTimeUpdateNode(currentNode.value, computedStyle, skeletonTheme)
}

/**
 * 独立主题数据创建
 */
function rootInsertIndependentNode () {
  const { left, top } = select('.x-mind-root-theme').node().getBoundingClientRect()
  return insertXmindNode([root], root._id, 'child', null, { x: (sourceEvent.value.pageX - left) / displaySheet.eventTransform.k, y: (sourceEvent.value.pageY - top) / displaySheet.eventTransform.k })
}

function nodeContextClick (event, _this) {
  event.stopPropagation()
  event.preventDefault()
  hiddenMapModals()
  removeNodeHighLight()
  hideElementControlNode()
  removeRelationNodeHighLight()
  currentNode.value = select(_this).datum()
  const id = currentNode.value.data._id
  nodeHighLight(id, skeletonTheme)
  sourceEvent.value = event
  contextMenuType.value = 'single'
  contextVisible.value = true
  selectNodes.value.push(currentNode.value)
}

function expandNodeClick (_event, _this) {
  const currentNode = select(_this).datum()
  toogleExpandXmindNode(root, currentNode.data._id, true)
  updateXmindCanvas()
}

function commentIconHandlerClick (_event, _this) {
  currentNode.value = select(_this).datum()
  commentVisible.value = true
}

function summaryHandlerDblclick (event, _this) {
  event.stopPropagation()
  const combinationId = select(_this).datum().parentId
  editorTypeName.value = 'summary'
  editorNodeValue.value = select(`#summary-path-${combinationId} .node-summary-description`).text()
  editorVisible.value = true
}

function relationNodeDBlclick (event, _this) {
  event.stopPropagation()
  editorTypeName.value = 'relation'
  const relationNodeSubject = select(_this).datum()
  relationId = relationNodeSubject.relationId
  relationSourceId = relationNodeSubject.source.id
  editorNodeValue.value = relationNodeSubject.relationText
  editorVisible.value = true
}

/**
 * 更新主题文字，超链接，关系说明，概要等数据信息
 * @param value
 * @param editorTypeName
 * @param hasChange
 * @param id
 */
function setEditableValue (value, editorTypeName, hasChange, id) {
  if (editorTypeName === 'relation') {
    const sourceData = getTargetDataById(root, relationSourceId)
    const relations = sourceData.relations
    const targetRelation = relations.find(o => o.relationId === relationId)
    targetRelation.relationText = value
  } else if (editorTypeName === 'summary') {
    updateSummaryText(root, value)
  } else {
    if (!hasChange) {
      const node = select(`#${id}`).select('.node-text-description').node()
      select(`#${id}`).select('.latex-span').style('margin-left', null)
      node && (node.style.display = 'block')
      return hiddenMapModals()
    }
    recursiveTreeValue(root, currentNode.value?.data?._id || id, editorTypeName, value)
  }
  updateXmindCanvas(false, hasChange)
  hiddenMapModals()
}

/**
 * 插入图片
 */
function setNodeImage (imageInfo) {
  if (!currentNode.value) return
  recursiveTreeValue(root, currentNode.value.data._id, 'imageInfo', imageInfo)
  uploadImageVisible.value = false
  updateXmindCanvas()
}

/**
 * 插入插画贴纸或者标记贴纸
 */
async function insertMarkerIcon (iconItem) {
  if (!currentNode.value) return
  if (!iconItem.type) {
    recursiveTreeValue(root, currentNode.value.data._id, 'imageInfo', iconItem)
    updateXmindCanvas(false)
  } else {
    const { data } = select(`#${currentNode.value.data._id}`).datum()
    const marks = data.marks || []
    const idx = marks.findIndex(m => m.type === iconItem.type)
    if (idx > -1) {
      marks.splice(idx, 1, iconItem)
    } else {
      marks.unshift(iconItem)
    }
    // 标记图例添加
    const { icon, name } = iconItem
    if (displaySheet.lengend?.markers) {
      displaySheet.lengend.markers[icon] = displaySheet.lengend.markers[icon] || name
    } else {
      displaySheet.lengend = {
        ...displaySheet.lengend,
        markers: { [icon]: name }
      }
    }
    recursiveTreeValue(root, currentNode.value.data._id, 'marks', marks)
    updateXmindCanvas(false)
  }
}

/**
 * 更新主题备注信息
 */
function updateTopicCommentHtml (html) {
  if (html !== '<p><br></p>') {
    recursiveTreeValue(root, currentNode.value.data._id, 'comment', html)
  } else {
    recursiveTreeValue(root, currentNode.value.data._id, 'comment', null)
  }
  commentVisible.value = false
  updateXmindCanvas()
}

/**
 * 更新主题公式
 * @param {*} latex
 */
function updateTopicLatex (latex) {
  recursiveTreeValue(root, currentNode.value.data._id, 'latex', latex)
  updateXmindCanvas(false)
}

/**
 * 画布标记图例显示显示隐藏切换
 */
function handleSwitchLengendShow (visible) {
  displaySheet.lengend = {
    ...displaySheet.lengend,
    visibility: visible
  }
  lengendVisible.value = visible
  // 首次显示图例的时候把图例定位到左上角
  if (visible && !displaySheet.lengend.position) {
    const { left, top } = select('.x-mind-root-theme').node().getBoundingClientRect()
    displaySheet.lengend.position = { x: -left + 20, y: -top + 80 }
  }
  graphLengends(displaySheet.lengend, root)
  storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
}

function updateXMindSkeleton (currentSkeleton) {
  displaySheet.skeleton = currentSkeleton
  mindContainer.selectAll('.mind-map-nodebox, .mind-map-edgebox').selectAll('*').remove()
  updateXmindCanvas(undefined, false)
  drawXmindAuxiliaryElement(skeletonTheme)
}

function updateXMindTheme (currentTheme) {
  displaySheet.gloabConfig = JSON.parse(JSON.stringify(gloabConfig.value))
  displaySheet.theme = currentTheme
  gloabConfig.value && (gloabConfig.value.backgroundColor = null)
  updateXmindCanvas(undefined, false)
}

function batchSelectTargetNodes (ids) {
  ids.forEach(id => {
    nodeHighLight(id, skeletonTheme)
    if (ids.length === 1) {
      currentNode.value = select(`#${id}`).datum()
    }
    selectNodes.value.push(select(`#${id}`).datum())
  })
}

function mouseStartMoveOnScreen (event) {
  if (event.buttons === 1 && event.ctrlKey) {
    domainStart = { x: event.x, y: event.y }
    hiddenMapModals()
    removeSummaryNodeHighLight()
    removeRelationNodeHighLight()
    hideElementControlNode()
    removeSelectOutBorder()
  }
  exitDrawRelation()
}

function mouseMoveIngOnScreen (event) {
  if (domainStart) {
    return drawAllSelectDomain(event, domainStart, updateSelectNodeHighLight)
  }
  updateRenderVirtualRelationPath(event, displaySheet.eventTransform)
  dragRelationControlMove(event, displaySheet.eventTransform)
  summaryControllerMove(svg, event, displaySheet.eventTransform)
  imageControlPointMove(event)
}

function mouseEndMoveOnScreen () {
  domainStart = null
  select('.all-select-domain').remove()
  if (selectNodes.value.length === 1) {
    currentNode.value = selectNodes.value[0]
  }
  drawRelationControlEnd(root, () => {
    storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
    appendXmindHistory()
    thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
  })
  moveSummaryControlEnd(root, () => {
    svg.classed('n-resize', false)
    svg.classed('e-resize', false)
    updateXmindCanvas()
    removeSummaryNodeHighLight()
  })
  imageControlMoveEnd(root, () => {
    svg.classed('nw-resize', false)
    svg.classed('ne-resize', false)
    updateXmindCanvas()
  })
}

function appendXmindHistory () {
  currentStep.value = 0
  historyStep.value.unshift(JSON.parse(JSON.stringify(root)))
}

function hiddenMapModals () {
  editorVisible.value = false
  contextVisible.value = false
  tiezhiIconVisible.value = false
  commentVisible.value = false
  importFileVisible.value = false
  uploadImageVisible.value = false
  topicPickVisible.value = false
  latexVisible.value = false
  shortKeyDescVisible.value = false
  subjectEditorStyle.value = null
  removeMarkHighlight()
}

function removeNodeHighLight () {
  selectAll('.select-node').classed('select-node', false).select('.select-rect-border').remove()
  currentNode.value = null
  selectNodes.value = []
  editorVisible.value = false
}

function handleXmindMapClick (event) {
  hiddenMapModals()
  removeNodeHighLight()
  removeSummaryNodeHighLight()
  removeRelationNodeHighLight()
  hideElementControlNode()
  removeSelectOutBorder()
  sourceEvent.value = event
}

/**
 * 导图画布导出为.json / .svg / .xmind / .gmind / .png / .jpeg
 * @param {*} type
 */
function handleExportMindMap (type) {
  handleXmindMapClick()
  if (type === 'JSON') {
    exportMindMapJson(displaySheet.name, displaySheet)
  } else if (type === 'SVG') {
    exportMindMapSvg(svg, gloabConfig.value?.backgroundColor || skeletonTheme.backgroundColor, displaySheet.name)
  } else if (type === 'XMIND') {
    exportMindMapToXmind(displaySheet, skeletonTheme.structure)
  } else if (type === 'GMIND') {
    downloadLocalFile(displaySheet.name, displaySheet)
  } else if (['PNG', 'JPEG'].includes(type)) {
    exportMinMapImage(mindBasicInfo.value.id, displaySheet.id, type.toLowerCase())
  } else if (type === 'PDF') {
    exportMinMapPDF(mindBasicInfo.value.id, displaySheet.id)
  }
}

function handlerActionClick (action) {
  switch (action) {
    case 'insert-brother': {
      const t = insertXmindNode(root.children || [], currentNode.value.data._id, 'brother')
      updateXmindCanvas()
      batchSelectTargetNodes([t._id])
      showNodeTextEditor(select(`#${t._id}`))
      break
    }
    case 'insert-child': {
      const t = insertXmindNode([root], currentNode.value.data._id, 'child')
      updateXmindCanvas()
      batchSelectTargetNodes([t._id])
      showNodeTextEditor(select(`#${t._id}`))
      break
    }
    case 'insert-tupian':
      uploadImageVisible.value = true
      break
    case 'insert-tag':
      editorTypeName.value = 'tag'
      editorVisible.value = true
      break
    case 'insert-marker':
      tiezhiIconVisible.value = true
      break
    case 'insert-latex':
      latexVisible.value = true
      break
    case 'insert-link':
      editorTypeName.value = 'link'
      editorVisible.value = true
      break
    case 'insert-beizhu': {
      commentVisible.value = true
      break
    }
    case 'insert-topiclink': {
      topicPickVisible.value = true
      break
    }
    case 'insert-relation': {
      insertTopicRelation(currentNode.value.data._id, skeletonTheme.relationStyle)
      break
    }
    case 'insert-summary': {
      const summaryId = handleInsertSummary(root, currentNode.value)
      summaryId && updateXmindCanvas(true, true, summaryId)
      removeNodeHighLight()
      hideElementControlNode()
      break
    }
    case 'insert-out-border': {
      const id = handleInsertOutBorder(root, currentNode.value)
      !id && updateXmindCanvas()
      removeNodeHighLight()
      break
    }
    case 'insert-task': {
      const update = handleInsertTask(root, currentNode.value)
      update && updateXmindCanvas()
      break
    }
    default:
      break
  }
}

function xmindMapEventMitters () {
  mitter.on('node-handler-click', function ({ event, _this }) {
    drawRelationPathEnd(_this, root, skeletonTheme.relationStyle, () => {
      storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
      appendXmindHistory()
      thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
    })
    nodeHandlerClick(event, _this)
  })
  mitter.on('node-context-click', function ({ event, _this }) {
    nodeContextClick(event, _this)
  })
  mitter.on('node-handler-dblclick', function ({ event, _this }) {
    nodeHandlerDblclick(event, _this)
  })
  mitter.on('node-handler-mouseenter', function ({ event, _this }) {
    nodeHandlerMouseEnter(event, _this)
  })
  mitter.on('node-handler-mouseleave', function ({ event, _this }) {
    nodeHandlerMouseLeave(event, _this)
  })
  mitter.on('node-handler-draging', function ({ event, _this }) {
    nodeHandlerDragIng(rc, displaySheet, event, _this)
  })
  mitter.on('set-lengend-position', function (position) {
    displaySheet.lengend.position = position
    storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
  })
  mitter.on('set-lengend-name', function (data) {
    if (displaySheet.lengend.markers) {
      displaySheet.lengend.markers[data.icon] = data.text
    } else {
      displaySheet.lengend.markers = {
        [data.icon]: data.text
      }
    }
    graphLengends(displaySheet.lengend, root)
    storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
  })
  mitter.on('node-handler-dragend', function ({ event, _this }) {
    const update = nodeHandlerDragEnd(root, event, _this)
    update && updateXmindCanvas()
  })
  mitter.on('handler-click-task-checkbox', function ({ event, _this }) {
    handleTaskCheckboxClick(event, _this, root)
    storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
  })
  mitter.on('expand-node-click', function ({ event, _this }) {
    event.stopPropagation()
    expandNodeClick(event, _this)
  })
  mitter.on('comment-handler-click', function ({ event, _this }) {
    commentIconHandlerClick(event, _this)
  })
  mitter.on('topiclink-handler-click', function (topiclink) {
    const { topicId, mapId } = topiclink
    handleAnchorTopic(topicId, mapId)
  })
  mitter.on('summary-handler-mouseenter', function ({ event, _this }) {
    summaryHandlerControllerDraw(null, event, _this)
  })
  mitter.on('summary-handler-mouseleave', function ({ event, _this }) {
    summaryHandlerMouseLeave(event, _this)
  })
  mitter.on('summary-handler-click', function ({ event, _this }) {
    removeNodeHighLight()
    hideElementControlNode()
    removeSelectOutBorder()
    removeRelationNodeHighLight()
    summaryNodeHandleClick(null, event, _this)
  })
  mitter.on('out-border-handler-click', function ({ event, _this }) {
    removeNodeHighLight()
    hideElementControlNode()
    removeSummaryNodeHighLight()
    outBorderNodeHandleClick(null, event, _this)
  })
  mitter.on('update-out-border-text', function ({ event }) {
    shortcutKeydown()
    updateOutBorderText(root, event)
    updateXmindCanvas()
  })
  mitter.on('summary-handler-dblclick', function ({ event, _this }) {
    summaryHandlerDblclick(event, _this)
  })
  mitter.on('image-handler-click', function () {
    removeNodeHighLight()
    removeSummaryNodeHighLight()
    removeRelationNodeHighLight()
    removeSelectOutBorder()
  })
  mitter.on('expand-handler-click', function ({ event, _this }) {
    event.stopPropagation()
    const currentNode = select(_this).datum()
    toogleExpandXmindNode(root, currentNode.data._id, false)
    updateXmindCanvas()
  })
  mitter.on('relation-node-handler-click', function () {
    removeNodeHighLight()
    hideElementControlNode()
    removeSummaryNodeHighLight()
  })
  mitter.on('relation-node-dblclick', function ({ event, _this }) {
    relationNodeDBlclick(event, _this)
  })
  mitter.on('get-relation-style', function (style) {
    relationStyle.value = style
  })
  mitter.on('get-summary-style', function (style) {
    summaryStyle.value = style
  })
  mitter.on('add-out-border-desc', function ({ event, _this }) {
    showOutBorderTextInput(event, _this, skeletonTheme, root, updateXmindCanvas)
  })
  const debounceUpdateStyle = debounce((filedName, filedValue) => {
    bacthUpdateNodeCustomStyle(root, selectNodes.value.map(o => o.data._id), filedName, filedValue)
    updateXmindCanvas(false)
  }, 300)
  const debounceGloabUpdateStyle = debounce((filedName, filedValue) => {
    gloabUpdateNodeCustomStyle(root, filedName, filedValue)
    updateXmindCanvas(false)
  }, 300)
  mitter.on('update-subject-style', function ({ filedName, filedValue, gloab }) {
    if (!debounceUpdateFileds.includes(filedName)) {
      if (!gloab) {
        bacthUpdateNodeCustomStyle(root, selectNodes.value.map(o => o.data._id), filedName, filedValue)
      } else {
        gloabUpdateNodeCustomStyle(root, filedName, filedValue)
      }
      updateXmindCanvas(false)
    } else {
      if (!gloab) {
        for (let i = 0; i < selectNodes.value.length; i++) {
          updateRedrawNodeStyle({ filedValue, filedName, id: selectNodes.value[i].data._id })
        }
        debounceUpdateStyle(filedName, filedValue)
      } else {
        gloabupdateColorStyle(root, filedName, filedValue)
        debounceGloabUpdateStyle(filedName, filedValue)
      }
    }
  })
  mitter.on('update-relation-style', function ({ styleName, styleValue }) {
    updateRelationStyle(styleName, styleValue, root, () => {
      storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
      appendXmindHistory()
      thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
    })
  })
  mitter.on('update-summary-style', function ({ styleName, styleValue }) {
    updateSummaryStyle(styleName, styleValue, root, () => {
      storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
      appendXmindHistory()
      thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
    })
  })
  mitter.on('reset-relation-style', function () {
    updateRelationFullStyle(root, null, updateXmindCanvas)
  })
  mitter.on('reset-summary-style', function () {
    updateSummaryFullStyle(root, null, updateXmindCanvas)
  })
  mitter.on('update-gloab-config', function (config) {
    gloabConfig.value = config
    displaySheet.gloabConfig = JSON.parse(JSON.stringify(config))
    updateXmindCanvas()
  })
  mitter.on('update-gloab-config-data', function (config) {
    gloabConfig.value = config
    displaySheet.gloabConfig = JSON.parse(JSON.stringify(config))
    storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
  })
}

function shortcutKeyMitters () {
  mitter.on('select-all', function () {
    hiddenMapModals()
    removeNodeHighLight()
    hideElementControlNode()
    const selections = select('.mind-map-nodebox').selectAll('.x-mind-nodetheme')
    selections.nodes().forEach(selection => {
      selectNodes.value.push(select(selection).datum())
      const id = select(selection).datum().data._id
      nodeHighLight(id, skeletonTheme)
    })
    if (selectNodes.value.length > 1) {
      currentNode.value = null
    } else {
      currentNode.value = selectNodes.value[0] || null
    }
  })
  mitter.on('insert-brother', function () {
    const ids = selectNodes.value.map(n => n.data._id)
    if (ids.length) {
      const tt = batchInsertXmindNode([root], ids, 'brother')
      updateXmindCanvas()
      batchSelectTargetNodes(tt.map(o => o._id))
      if (tt.length === 1) {
        showNodeTextEditor(select(`#${tt.map(o => o._id)[0]}`))
      }
    }
  })
  mitter.on('insert-child', function () {
    const ids = selectNodes.value.map(n => n.data._id)
    if (ids.length) {
      const tt = batchInsertXmindNode([root], ids, 'child')
      updateXmindCanvas()
      batchSelectTargetNodes(tt.map(o => o._id))
      if (tt.length === 1) {
        showNodeTextEditor(select(`#${tt.map(o => o._id)[0]}`))
      }
    }
  })
  mitter.on('move-up', function () {
    if (currentNode.value) {
      moveXmindNode(root.children || [], currentNode.value.data._id, 'up')
      updateXmindCanvas(false)
    }
  })
  mitter.on('move-down', function () {
    if (currentNode.value) {
      moveXmindNode(root.children || [], currentNode.value.data._id, 'down')
      updateXmindCanvas(false)
    }
  })
  mitter.on('copy', function () {
    if (currentNode.value) {
      isPastState = true
      copyNodeInstance.value = copyOrCutXmindNode(root.children, root, currentNode.value.data._id)
      hiddenMapModals()
    }
  })
  mitter.on('cut', function () {
    if (currentNode.value && currentNode.value.parent) {
      isPastState = false
      copyNodeInstance.value = copyOrCutXmindNode(root.children, root, currentNode.value.data._id, true)
      updateXmindCanvas()
    }
  })
  mitter.on('past', function () {
    if (currentNode.value && copyNodeInstance.value) {
      const paseNode = JSON.parse(JSON.stringify(copyNodeInstance.value))
      insertXmindNode([root], currentNode.value.data._id, 'child', paseNode)
      updateXmindCanvas()
      if (!isPastState) {
        copyNodeInstance.value = null
      }
    }
  })
  mitter.on('delete', function () {
    // 删除节点
    const ids = selectNodes.value.map(n => n.data._id)
    if (ids.length) {
      batchDeleteXmindNode(root.children, ids)
      return updateXmindCanvas()
    }

    // 删除节点内的marker标记
    deleteIconMark(root, updateXmindCanvas)

    // 删除概要
    handleDeleteSummary(root, updateXmindCanvas)

    // 删除图片
    deleteTopicImage(root, updateXmindCanvas)

    // 删除关系连线
    deleteRelationPath(root, () => {
      storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
      appendXmindHistory()
      thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
      hiddenMapModals()
    })

    // 删除节点外边框
    deleteNodeOutBorder(root, updateXmindCanvas)
  })
  mitter.on('copy-style', function () {
    if (currentNode.value) {
      copyStyle.value = getNodeCustomStyle(currentNode.value.data)
    }
  })
  mitter.on('reference-style', function () {
    if (selectNodes.value?.length) {
      const ids = selectNodes.value.map(n => n.data._id)
      batchReferenceStyle(root, ids, copyStyle.value)
      updateXmindCanvas()
    } else if (currentNode.value) {
      batchReferenceStyle(root, [currentNode.value.data._id], copyStyle.value)
      updateXmindCanvas()
    }
  })
  mitter.on('step-prev', function () {
    if (historyStep.value.length > 1 && currentStep.value < historyStep.value.length - 1) {
      currentStep.value += 1
      displaySheet.rootTopic = JSON.parse(JSON.stringify(historyStep.value[currentStep.value]))
      root = displaySheet.rootTopic
      updateXmindCanvas(true, false)
    }
  })
  mitter.on('step-next', function () {
    if (currentStep.value >= 1) {
      currentStep.value -= 1
      displaySheet.rootTopic = JSON.parse(JSON.stringify(historyStep.value[currentStep.value]))
      root = displaySheet.rootTopic
      updateXmindCanvas(true, false)
    }
  })
  mitter.on('reset-style', function () {
    if (selectNodes.value?.length) {
      const ids = selectNodes.value.map(n => n.data._id)
      batchResetNodeStyle(root, ids)
      updateXmindCanvas()
    } else if (currentNode.value) {
      batchResetNodeStyle(root, [currentNode.value.data._id])
      updateXmindCanvas()
    }
  })
  mitter.on('gloab-search', function () {
    handleXmindMapClick()
    topicPickVisible.value = true
  })
  mitter.on('map-center', function () {
    setXMindMapCenter(undefined, 300)
    hiddenMapModals()
  })
}

/**
 * 右击菜单事件
 * @param {*} operate 事件类型
 * @param {*} type 是否批量操作 single, batch
 * @param {*} key 子节点编码类型或者需要删除主题元素的key值
 */
function contextMenuClick (operate, type, key) {
  const ids = selectNodes.value.map(n => n.data._id)
  switch (operate) {
    case 'insert-independent': {
      const t = rootInsertIndependentNode()
      updateXmindCanvas()
      batchSelectTargetNodes([t._id])
      showNodeTextEditor(select(`#${t._id}`))
      break
    }
    case 'insert-brother':
      if (type === 'single') {
        const t = insertXmindNode(root.children || [], currentNode.value.data._id, 'brother')
        updateXmindCanvas()
        batchSelectTargetNodes([t._id])
        showNodeTextEditor(select(`#${t._id}`))
      } else if (type === 'global') {
        const tt = batchInsertXmindNode(root.children || [], ids, 'brother')
        updateXmindCanvas()
        batchSelectTargetNodes(tt.map(o => o._id))
      }
      break
    case 'insert-child':
      if (type === 'single') {
        const t = insertXmindNode([root], currentNode.value.data._id, 'child')
        updateXmindCanvas()
        batchSelectTargetNodes([t._id])
        showNodeTextEditor(select(`#${t._id}`))
      } else if (type === 'global') {
        const tt = batchInsertXmindNode(root.children || [], ids, 'child')
        updateXmindCanvas()
        batchSelectTargetNodes(tt.map(o => o._id))
      }
      break
    case 'insert-serial':
      recursiveTreeValue(root, currentNode.value.data._id, 'child-serial', key)
      updateXmindCanvas()
      break
    case 'insert-tupian':
      contextVisible.value = false
      uploadImageVisible.value = true
      break
    case 'insert-marker':
      contextVisible.value = false
      tiezhiIconVisible.value = true
      break
    case 'insert-latex':
      contextVisible.value = false
      latexVisible.value = true
      break
    case 'insert-tag':
      contextVisible.value = false
      editorTypeName.value = 'tag'
      editorVisible.value = true
      break
    case 'insert-link':
      contextVisible.value = false
      editorTypeName.value = 'link'
      editorVisible.value = true
      break
    case 'insert-topiclink':
      contextVisible.value = false
      topicPickVisible.value = true
      break
    case 'insert-task': {
      const update = handleInsertTask(root, currentNode.value)
      update && updateXmindCanvas()
      break
    }
    case 'insert-beizhu': {
      contextVisible.value = false
      commentVisible.value = true
      break
    }
    case 'up':
      moveXmindNode(root.children || [], currentNode.value.data._id, 'up')
      updateXmindCanvas(false)
      break
    case 'down':
      moveXmindNode(root.children || [], currentNode.value.data._id, 'down')
      updateXmindCanvas(false)
      break
    case 'copy':
      isPastState = true
      copyNodeInstance.value = copyOrCutXmindNode(root.children, root, currentNode.value.data._id)
      hiddenMapModals()
      break
    case 'copy-style':
      copyStyle.value = getNodeCustomStyle(currentNode.value.data)
      hiddenMapModals()
      break
    case 'reference-style':
      if (type === 'single') {
        batchReferenceStyle(root, [currentNode.value.data._id], copyStyle.value)
      } else if (type === 'global') {
        batchReferenceStyle(root, ids, copyStyle.value)
      }
      updateXmindCanvas()
      break
    case 'reset-style':
      if (type === 'single') {
        batchResetNodeStyle(root, [currentNode.value.data._id])
      } else if (type === 'global') {
        batchResetNodeStyle(root, ids)
      }
      updateXmindCanvas()
      break
    case 'cut':
      isPastState = false
      copyNodeInstance.value = copyOrCutXmindNode(root.children, root, currentNode.value.data._id, true)
      updateXmindCanvas()
      break
    case 'delete':
      if (type === 'single') {
        const newList = key === 'only-self' ? currentNode.value.data.children : undefined
        deleteXmindNode(root.children || [], currentNode.value.data._id, newList)
      } else if (type === 'global') {
        batchDeleteXmindNode(root.children || [], ids)
      }
      updateXmindCanvas()
      break
    case 'delete-element':
      if (type === 'single') {
        recursiveTreeValue(root, currentNode.value.data._id, key, null)
      } else if (type === 'global') {
        batchRecursiveTreeValue(root, ids, key, null)
      }
      updateXmindCanvas()
      break
    case 'past': {
      const paseNode = JSON.parse(JSON.stringify(copyNodeInstance.value))
      insertXmindNode([root], currentNode.value.data._id, 'child', paseNode)
      updateXmindCanvas()
      if (!isPastState) {
        copyNodeInstance.value = null
      }
      break
    }
    case 'noexpand-all':
      expandAllNodes(root, false)
      updateXmindCanvas()
      break
    case 'expand-all':
      expandAllNodes(root, true)
      updateXmindCanvas()
      break
    case 'map-center':
      setXMindMapCenter(undefined, 300)
      hiddenMapModals()
      break
    case 'copy-relation-style':
      copyRelationStyle.value = JSON.parse(JSON.stringify(relationStyle.value))
      hiddenMapModals()
      break
    case 'past-relation-style':
      updateRelationFullStyle(root, copyRelationStyle.value, updateXmindCanvas)
      break
    case 'reset-relation-style':
      updateRelationFullStyle(root, null, updateXmindCanvas)
      break
    case 'delete-relation':
      deleteRelationPath(root, () => {
        storageRootRelaTime(sheets, displaySheet, mindBasicInfo.value?.id)
        appendXmindHistory()
        thumbXminRef.value && thumbXminRef.value.updateThumbXmindMap()
        hiddenMapModals()
      })
      break
    case 'copy-summary-style':
      copySummaryStyle.value = JSON.parse(JSON.stringify(summaryStyle.value))
      hiddenMapModals()
      break
    case 'past-summary-style':
      updateSummaryFullStyle(root, copySummaryStyle.value, updateXmindCanvas)
      break
    case 'delete-summary':
      handleDeleteSummary(root, updateXmindCanvas)
      break
    case 'reset-summary-style':
      updateSummaryFullStyle(root, null, updateXmindCanvas)
      break
  }
}
</script>

<style lang="scss">
@import './css/gloab.scss';
</style>

<style scoped lang="scss">
@import './css/common.scss';
</style>
